Skip to content

feat(search): add Exa Search API as a search provider#547

Open
lmorchard wants to merge 1 commit into
mainfrom
worktree-exa-search-provider
Open

feat(search): add Exa Search API as a search provider#547
lmorchard wants to merge 1 commit into
mainfrom
worktree-exa-search-provider

Conversation

@lmorchard

Copy link
Copy Markdown
Collaborator

Summary

Adds Exa (exa-api) as a search provider alongside the existing parallel-api / google / bing / duckduckgo providers. It slots into the existing provider abstraction — no changes to the webSearch tool, SearchService, or the agent loop.

ExaSearchProvider POSTs to https://api.exa.ai/search, opting into contents.highlights so results include snippets (Exa returns metadata only by default), and maps url/title/highlights through the same markdown + security-wrapper path as the Parallel provider.

Changes

  • config (config/defaults.ts): "exa-api" added to SEARCH_PROVIDERS; new exa_api_key field (env EXA_API_KEY, CLI --exa-api-key).
  • factory + webAgent: exa-api case in createSearchProvider; key-required validation guard mirroring parallel-api.
  • key selection (run.ts, taskRunner.ts): the agent exposes a single provider-agnostic searchApiKey, so call sites now pick exa_api_key vs parallel_api_key by provider. The server also gained exa-api in its inline provider union and a key-required validation branch.
  • debug logging for both API providers (gated on --debug, via the [X:debug] console.warn convention):
    • request: the exact outbound body (query + options, API key omitted)
    • response: result count plus an abbreviated sample of the first result so all returned fields are visible, with long strings truncated (shared abbreviateForDebug helper)

Note: highlights-only, text/summary intentionally not enabled

Exa can also return text (full page markdown) and summary (an LLM-generated abstract) via contents.text / contents.summary. This PR intentionally requests only highlights to keep parity with the Parallel provider's excerpts and avoid the extra token/cost overhead (Exa bills for summary generation). The debug response sample confirms Exa otherwise returns id / image / favicon beyond what we map; Parallel returns only url / title / excerpts. Enabling text/summary later is a one-line change in the request body, ideally behind a config knob.

Testing

  • Unit tests cover both providers: factory creation/validation, markdown formatting, empty/missing-title results, API error, the Exa highlights opt-in, and debug request/response logging on and off.
  • Verified live against the real Exa and Parallel APIs through the full agent loop (pilo run ... --search-provider exa-api --debug), confirming the search step, key wiring, and debug output.
  • Full suite green: core 913, cli 229, server 103, extension 273. Typecheck, prettier, and gitleaks all clean.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings June 16, 2026 00:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Exa (exa-api) as an additional API-backed search provider within the existing search provider abstraction, including config wiring and debug logging improvements for API providers.

Changes:

  • Added exa-api provider support (provider factory, provider implementation, config keys, and server/CLI key wiring).
  • Introduced debug logging for API providers (request body + abbreviated response sample).
  • Expanded unit tests to cover Exa provider behavior and debug logging behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/server/src/taskRunner.ts Adds exa-api to request typing, validates server EXA key presence, and selects a provider-specific search API key.
packages/core/test/search/searchProvider.test.ts Adds Exa provider tests and debug logging tests for both API providers.
packages/core/test/config.test.ts Updates config key coverage to include exa_api_key.
packages/core/src/webAgent.ts Validates exa-api requires a key and forwards debug to SearchService.create(...).
packages/core/src/search/searchProvider.ts Extends provider factory to support exa-api and a debug option.
packages/core/src/search/providers/parallelSearch.ts Adds gated debug logging and abbreviated response sampling via helper.
packages/core/src/search/providers/exaSearch.ts New Exa search provider implementation with highlights opt-in and debug logging.
packages/core/src/search/debugPreview.ts Adds abbreviateForDebug helper to truncate long strings in debug samples.
packages/core/src/config/defaults.ts Adds exa-api to SEARCH_PROVIDERS and introduces exa_api_key config field (env/CLI).
packages/cli/src/commands/run.ts Selects search API key based on provider (now including exa-api).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +389 to 392
searchProvider,
searchApiKey:
searchProvider === "exa-api" ? serverConfig.exa_api_key : serverConfig.parallel_api_key,
tabstackApiKey: body.tabstackApiKey ?? serverConfig.tabstack_api_key,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 915f816searchApiKey is now undefined for browser providers and none; only parallel-api and exa-api get their respective key.

Comment thread packages/cli/src/commands/run.ts Outdated
Comment on lines +344 to +348
searchProvider: options.searchProvider ?? cfg.search_provider,
searchApiKey: cfg.parallel_api_key,
searchApiKey:
(options.searchProvider ?? cfg.search_provider) === "exa-api"
? cfg.exa_api_key
: cfg.parallel_api_key,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 915f816searchApiKey is now undefined for browser providers and none; only parallel-api and exa-api get their respective key.

Add exa-api alongside the existing parallel-api/google/bing/duckduckgo
providers. ExaSearchProvider POSTs to api.exa.ai/search, opting into
contents.highlights so results include snippets (Exa returns metadata
only by default), and maps url/title/highlights through the shared
markdown + security-wrapper path identical to the Parallel provider.

Wiring:
- config: add "exa-api" to SEARCH_PROVIDERS and an exa_api_key field
  (env EXA_API_KEY, --exa-api-key)
- factory + webAgent: exa-api case and key-required validation guard
- run.ts / taskRunner.ts: select the API key by provider, passing
  undefined for providers that don't use one (browser providers, none)
  so an unrelated key isn't threaded through the agent config

Debug logging for both API providers (Exa and Parallel), gated on
--debug via the [X:debug] console.warn convention:
- request: the exact outbound body (query + options, API key omitted)
- response: result count plus an abbreviated sample of the first result
  so all returned fields are visible (long strings truncated by the
  shared abbreviateForDebug helper)
The debug flag threads through CreateSearchProviderOptions, which
SearchService.create already forwards.

Tests cover the factory, markdown formatting, empty/missing-title
results, API error, the highlights opt-in, and debug request/response
logging on and off for both API providers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmorchard lmorchard force-pushed the worktree-exa-search-provider branch from 915f816 to 33e7da8 Compare June 16, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants